package com.meshnetics.gb.stack.mac.primitives;

import com.meshnetics.gb.stack.core.event.StackConfirm;
import com.meshnetics.gb.stack.core.event.StackEvent;
import com.meshnetics.gb.stack.core.event.StackEventType;
import com.meshnetics.gb.stack.core.layer.StackLayer;
import com.meshnetics.gb.stack.mac.RadioMACStackLayer;
import com.meshnetics.gb.stack.mac.type.*;
import com.meshnetics.gb.type.impl.ByteUtils;
import com.meshnetics.gb.type.impl.zcl.TypeUInt;
import com.meshnetics.gb.type.impl.zcl.UInt;

import java.nio.ByteBuffer;

/**
 * WRITE DESCRIPTION HERE
 *
 * @author AKovylin
 */
public class MACScanConfirm extends StackConfirm implements MACReplyOnCommand {
    public static final byte CODE = 0x59;

    private static final byte[] ALLOWED_STATUSES = new byte[]{
            MACStatus.SUCCESS.getCode(),
            MACStatus.NO_BEACON.getCode(),
            MACStatus.INVALID_PARAMETER.getCode(),
    };

    private final ScanType scanType;
    private final ChannelMask unscannedChannels;
    private final UInt resultListSize;
    private final EnergyDetectList energyDetectList;
    private final PANDescriptorList panDescriptorList;

    public MACScanConfirm(ByteBuffer bb, RadioMACStackLayer layer) {
        super("macScanConfirm", StackEventType.LME_TYPE, null, null, layer);
        ByteUtils.ensureNotNull(bb);
        ByteUtils.ensureCapacity(bb, 7);
        MACStatus status = (MACStatus) TypeMACStatus.TYPE.deserialize(bb);
        Utils.checkStatus(status, ALLOWED_STATUSES);
        setStatus(status);
        this.scanType = (ScanType) TypeScanType.TYPE.deserialize(bb);
        this.unscannedChannels = (ChannelMask) TypeChannelMask.TYPE.deserialize(bb);
        this.resultListSize = (UInt) TypeUInt.UINT_8.deserialize(bb);
        int size = ((Number) resultListSize.toObject()).intValue();
        if (ScanType.ED_SCAN.equals(scanType)) {
            this.energyDetectList = (EnergyDetectList) TypeEnergyDetectList.TYPE.deserialize(bb, size);
            this.panDescriptorList = null;
        } else {
            this.energyDetectList = null;
            this.panDescriptorList = (PANDescriptorList) TypePANDescriptorList.TYPE.deserialize(bb, size);
        }
        if (bb.remaining() > 0) {
            throw new IllegalArgumentException("unexpected content of ByteBuffer, " + bb.remaining() + " bytes left");
        }
    }

    public MACScanConfirm(MACStatus status, ScanType scanType, ChannelMask unscannedChannels, UInt resultListSize,
                          EnergyDetectList energyDetectList, PANDescriptorList panDescriptorList) {
        this(status, scanType, unscannedChannels, resultListSize, energyDetectList, panDescriptorList, null);
    }

    public MACScanConfirm(MACStatus status, ScanType scanType, ChannelMask unscannedChannels, UInt resultListSize,
                          EnergyDetectList energyDetectList, PANDescriptorList panDescriptorList,
                          StackLayer source) {
        super("macScanConfirm", StackEventType.LME_TYPE, null, null, source);
        if (status == null) {
            throw new NullPointerException("status is null");
        }
        if (scanType == null) {
            throw new NullPointerException("scanType is null");
        }
        if (unscannedChannels == null) {
            throw new NullPointerException("unscannedChannels is null");
        }
        if (resultListSize == null) {
            throw new NullPointerException("resultListSize is null");
        }
        if (status == MACStatus.SUCCESS && scanType != ScanType.ORPHAN_SCAN
                && (energyDetectList == null && panDescriptorList == null)) {
            throw new IllegalArgumentException("both lists are null");
        }
        // no check for fitting scanType
        if (energyDetectList != null && panDescriptorList != null) {
            throw new IllegalArgumentException("only one list corresponding to scanType must be not null");
        }
        if (status == MACStatus.SUCCESS && ScanType.ED_SCAN.equals(scanType) && energyDetectList == null) {
            throw new IllegalArgumentException("energyDetectList must be non null for energy detect scan");
        }
        if (status == MACStatus.SUCCESS && !ScanType.ED_SCAN.equals(scanType) && !ScanType.ORPHAN_SCAN.equals(scanType)
                && panDescriptorList == null) {
            throw new IllegalArgumentException("panDescriptorList must be non null for active/passive scan");
        }
        Utils.checkStatus(status, ALLOWED_STATUSES);
        setStatus(status);
        this.scanType = scanType;
        this.unscannedChannels = unscannedChannels;
        this.resultListSize = resultListSize;
        this.energyDetectList = energyDetectList;
        this.panDescriptorList = panDescriptorList;
    }

    public ScanType getScanType() {
        return scanType;
    }

    public ChannelMask getUnscannedChannels() {
        return unscannedChannels;
    }

    public UInt getResultListSizeType() {
        return resultListSize;
    }

    public Byte getResultListSize() {
        return (Byte) resultListSize.toObject();
    }

    public EnergyDetectList getEnergyDetectList() {
        return energyDetectList;
    }

    public PANDescriptorList getPanDescriptorList() {
        return panDescriptorList;
    }

    public boolean isReplyOn(StackEvent se) {
        return se instanceof MACScanRequest && type.equals(((MACScanRequest) se).getEventType());
    }

    public String toString() {
        return getName() + '(' + getStatus() + ',' + scanType + ',' + unscannedChannels + ',' + resultListSize + ',' +
                energyDetectList + ',' + panDescriptorList + ')';
    }
}
